﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Threading;
using MyAlbum.Model;

namespace MyAlbum.Data.Xml
{
    public class BlogRepsitory : IBlogRepository
    {
        const string DateFormat = "yyyy.MM.dd-HH.mm.ss";
        private string _blogsDirectory;
        private XmlSerializer serializer;
        private static ReaderWriterLock rwl = new ReaderWriterLock();
        private XmlSerializer Serializer
        {
            get
            {
                if (serializer == null)
                {
                    serializer = new XmlSerializer(typeof(BlogML.Xml.BlogMLPost));
                }
                return serializer;
            }
        }
        public BlogRepsitory(string rootDirectory)
        {
            if (rootDirectory == null)
            {
                rootDirectory = Directory.GetCurrentDirectory();
            }
            _blogsDirectory = Path.Combine(rootDirectory, "Blogs");
            if (!Directory.Exists(_blogsDirectory))
            {
                Directory.CreateDirectory(_blogsDirectory);
            }
        }
        private DateTime ParseDate(string fileName)
        {
            DateTime dt;
            int index;
            if ((index = fileName.IndexOf("_")) > -1)
            {
                DateTime.TryParse(fileName.Substring(0, index), out dt);
            }
            else
            {
                //return min value and the filter will no catch it anyway
                dt = DateTime.MinValue;
            }
            return dt;
        }
        private BlogML.Xml.BlogMLPost Deserialize(string filePath)
        {
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                return BlogML.Xml.BlogMLSerializer.Deserialize(stream).Posts[0];
            }
        }
        public IEnumerable<BlogML.Xml.BlogMLPost> GetPosts(int pageIndex, int pageSize, DateTime? sinceDate)
        {
            Func<string, bool> filter = s => sinceDate.HasValue ? ParseDate(s) >= sinceDate.Value : true;
            return Directory.GetFiles(this._blogsDirectory).Where(filter).Skip(((pageIndex - 1) * pageSize)).Take(pageSize).Select(s => Deserialize(s));
        }

        public IEnumerable<BlogML.Xml.BlogMLPost> GetPosts(BlogML.Xml.BlogMLCategory tag, DateTime? sinceDate, int maxCount)
        {
            Func<string, bool> filter = s => sinceDate.HasValue ? ParseDate(s) >= sinceDate.Value : true;

            Func<BlogML.Xml.BlogMLPost, bool> tagFilter = b =>
            {
                if (tag != null)
                {
                    foreach (BlogML.Xml.BlogMLCategoryReference item in b.Categories)
                    {
                        if (tag.Title.Equals(item.Ref, StringComparison.InvariantCultureIgnoreCase))
                        {
                            return true;
                        }
                    }
                    return false;
                }
                else
                {
                    return true;

                }
            };

            return Directory.GetFiles(this._blogsDirectory).Where(filter).Select(s => Deserialize(s)).Where(tagFilter).Take(maxCount);
        }

        public IEnumerable<BlogML.Xml.BlogMLPost> GetPosts(string searchFilter, DateTime? sinceDate, int maxCount)
        {
            Func<string, bool> filter = s => (sinceDate.HasValue ? ParseDate(s) >= sinceDate.Value : true);
            Func<BlogML.Xml.BlogMLPost, bool> contentFilter = b => searchFilter != null ? b.Content.Text.Contains(searchFilter) : true;
            return Directory.GetFiles(this._blogsDirectory).Where(filter).Select(s => Deserialize(s)).Where(contentFilter).Take(maxCount);
        }
        private Func<string, bool> GetIDFilter(string postId)
        {
            Func<string, bool> filter = s =>
            {
                s = Path.GetFileNameWithoutExtension(s);
                var index = s.IndexOf("_");
                if (index > -1)
                {
                    return s.Substring(index + 1) == postId;
                }
                else
                {
                    return false;
                }
            };
            return filter;
        }
        public BlogML.Xml.BlogMLPost GetPost(string postId)
        {

            return Directory.GetFiles(this._blogsDirectory).Where(GetIDFilter(postId)).Select(s => Deserialize(s)).SingleOrDefault();
        }
        private string GetFileName(DateTime dt, string id)
        {
            return dt.ToString(DateFormat) + "_" + id + ".xml";
        }
        public string AddPost(BlogML.Xml.BlogMLPost post)
        {
            Serialize(GetFileName(post.DateCreated, post.ID), post);
            return post.ID;
        }

        private void Serialize(string fileName, BlogML.Xml.BlogMLPost post)
        {
            var des = new BlogML.Xml.BlogMLBlog();
            des.Posts.Add(post);
            using (var stream = new FileStream(Path.Combine(this._blogsDirectory, fileName), FileMode.CreateNew, FileAccess.Write))
            {
                BlogML.Xml.BlogMLSerializer.Serialize(stream, des);
            }
        }


        public void UpdatePost(BlogML.Xml.BlogMLPost post)
        {
            rwl.AcquireReaderLock(10000);
            try
            {
                string fileName = Directory.GetFiles(this._blogsDirectory).Where(GetIDFilter(post.ID)).SingleOrDefault();
                if (fileName != null)
                {
                    File.Copy(fileName, fileName + "_back");
                    try
                    {
                        File.Delete(fileName);
                        AddPost(post);
                    }
                    catch
                    {
                        File.Copy(fileName + "_back", fileName);
                        throw;
                    }
                    finally
                    {
                        File.Delete(fileName + "_back");
                    }
                }
                else
                {
                    AddPost(post);
                }
            }
            finally
            {
                rwl.ReleaseLock();
            }
        }

        public void DeletePost(string postId)
        {
            string fileName = Directory.GetFiles(this._blogsDirectory).Where(GetIDFilter(postId)).SingleOrDefault();
            if (fileName != null)
            {
                File.Delete(fileName);
            }
        }

        public string AddComment(string postId, BlogML.Xml.BlogMLComment comment)
        {
            var post = GetPost(postId);
            if (post != null)
            {
                post.Comments.Add(comment);
                UpdatePost(post);
            }
            return comment.ID;
        }

        public void UpdateComment(string postId, BlogML.Xml.BlogMLComment comment)
        {
            var post = GetPost(postId);
            if (post != null)
            {
                BlogML.Xml.BlogMLComment c = null;
                foreach (BlogML.Xml.BlogMLComment item in post.Comments)
                {
                    if (item.ID == comment.ID)
                    {
                        c = item;
                        break;
                    }
                }
                c.Approved = comment.Approved;
                c.Content = comment.Content;
                c.DateModified = comment.DateModified;
                c.Title = comment.Title;
                c.UserEMail = comment.UserEMail;
                c.UserName = comment.UserName;
                c.UserUrl = comment.UserUrl;
                UpdatePost(post);
            }
        }

        public void DeleteComment(string postId, string commentId)
        {
            var post = GetPost(postId);
            if (post != null)
            {
                BlogML.Xml.BlogMLComment c = null;
                foreach (BlogML.Xml.BlogMLComment item in post.Comments)
                {
                    if (item.ID == commentId)
                    {
                        c = item;
                        break;
                    }
                }
                post.Comments.Remove(c);
                UpdatePost(post);
            }
        }


        public BlogML.Xml.BlogMLPost GetPostByTitle(string title)
        {
            return Directory.GetFiles(this._blogsDirectory).Select(s => Deserialize(s)).Where(b => b.Title == title).SingleOrDefault();
        }

        public List<Tag> GetCategories()
        {
            List<Tag> items = new List<Tag>();
            foreach (var cat in Directory.GetFiles(this._blogsDirectory).Select(s => Deserialize(s)).Select(b => b.Categories))
            {
                foreach (BlogML.Xml.BlogMLCategoryReference item in cat)
                {
                    Tag t = items.SingleOrDefault(s => s.Name == item.Ref);
                    if (t != null)
                    {
                        t.Count++;
                    }
                    else
                    {
                        t = new Tag()
                        {
                            Id = item.Ref,
                            Count = 1,
                            Name = item.Ref
                        };
                        items.Add(t);
                    }
                }
            }
            return items;
        }


        public int CountPosts()
        {
            return Directory.GetFiles(this._blogsDirectory).Count();
        }

    }
}
